Skip to content

Feature/meeting with registry caserecord casefile support - #53

Merged
gvaguirres merged 50 commits into
mainfrom
feature/meeting-with-registry-caserecord-casefile-support
Apr 27, 2026
Merged

Feature/meeting with registry caserecord casefile support#53
gvaguirres merged 50 commits into
mainfrom
feature/meeting-with-registry-caserecord-casefile-support

Conversation

@MartinStenhagen

@MartinStenhagen MartinStenhagen commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

resolves #47

Summary by CodeRabbit

  • New Features

    • Admin meetings UI: create, view, edit, delete meetings with agenda management and document linking (HTMX-driven)
    • Agenda item controls: add/move/remove items and attach/remove case-file documents
    • Meetings page styling and layout
  • Bug Fixes

    • Prevent deletion of files in use as meeting documents; user-facing error mapped to 409/conflict
    • Improved error handling/messages for meeting-related operations

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a full meetings feature (domain models, repositories, service, controller, UI, styles, migrations, tests), integrates meeting-aware protections into case-file deletion, introduces several meeting-related exceptions and global handlers, and updates docker-compose LocalStack environment variables.

Changes

Cohort / File(s) Summary
Docker & Environment
docker-compose.yaml
Switches LocalStack env entries to YAML key/value format and adds AWS_BUCKET_NAME defaulting to team4you-files.
Meeting Domain & DTOs
src/main/java/backendlab/team4you/meeting/Meeting.java, src/main/java/backendlab/team4you/meeting/MeetingStatus.java, src/main/java/backendlab/team4you/meeting/MeetingAgendaItem.java, src/main/java/backendlab/team4you/meeting/MeetingAgendaDocument.java
Adds JPA entities and enum for meetings, agenda items, and agenda documents (relations, lifecycle callbacks, constraints).
Meeting Repositories
src/main/java/backendlab/team4you/meeting/MeetingRepository.java, src/main/java/backendlab/team4you/meeting/MeetingAgendaItemRepository.java, src/main/java/backendlab/team4you/meeting/MeetingAgendaDocumentRepository.java
New Spring Data repositories with derived query methods for listing, existence checks, and ordering.
Meeting Service & Controller
src/main/java/backendlab/team4you/meeting/MeetingService.java, src/main/java/backendlab/team4you/meeting/MeetingController.java
Implements transactional meeting service (validation, create/update/delete, agenda item ordering, document linking) and HTMX-aware admin controller with endpoints for meetings, agenda items, and documents.
Case File Integration
src/main/java/backendlab/team4you/casefile/CaseFileRepository.java, src/main/java/backendlab/team4you/casefile/CaseFileService.java, src/main/java/backendlab/team4you/casefile/ui/CaseFileViewController.java
Adds finder ordered by uploadedAt desc; injects MeetingAgendaDocumentRepository into service and prevents deletion if file is referenced by a meeting (throws FileInUseException); view controller handles that exception.
Exceptions & Handlers
src/main/java/backendlab/team4you/exceptions/...
Adds multiple meeting-related exceptions (MeetingNotFoundException, MeetingAgendaItemNotFoundException, MeetingAgendaDocumentNotFoundException, InvalidMeetingStateException, DuplicateMeetingAgendaItemException, DuplicateMeetingAgendaDocumentException, FileInUseException) and extends REST/view handlers to map them to appropriate HTTP/status/view flows.
Database Migrations
src/main/resources/db/migration/V19__create_meeting_tables.sql, src/main/resources/db/migration/V20__make_meeting_agenda_order_constraint_deferrable.sql
Adds migrations to create meeting-related tables with FK cascade and unique constraints; makes agenda_order unique constraint deferrable to support resequencing.
UI Templates & Styling
src/main/resources/templates/admin-layout.html, src/main/resources/templates/fragments/admin-sidenav.html, src/main/resources/templates/fragments/admin-meetings.html, src/main/resources/static/css/meetings.css, src/main/resources/static/css/admin.css
Adds meetings stylesheet, updates admin layout to include HTMX and meetings stylesheet, adds admin sidenav link, and new admin meetings fragment (HTMX-driven two-pane UI). Minor newline fix in admin.css.
Tests
src/test/java/backendlab/team4you/meeting/MeetingControllerTest.java, src/test/java/backendlab/team4you/meeting/MeetingServiceTest.java, src/test/java/backendlab/team4you/casefile/CaseFileServiceTest.java
Adds comprehensive controller and service tests for meetings (creation, update, delete, agenda operations, document linking) and augments case-file tests to cover deletion guard when files are used in meetings.

Sequence Diagram(s)

sequenceDiagram
    participant Admin as Admin UI
    participant Controller as MeetingController
    participant Service as MeetingService
    participant Repos as Repositories<br/>(Meeting, Registry, CaseRecord, CaseFile, Agenda*)
    participant DB as Database

    Admin->>Controller: POST /admin/meetings (title, startsAt, ...)
    Controller->>Service: createMeeting(registryId, title, startsAt, ...)
    Service->>Repos: validate registry & inputs
    Repos-->>Service: registry found
    Service->>Repos: save Meeting
    Repos->>DB: INSERT meeting
    DB-->>Repos: id assigned
    Repos-->>Service: persisted
    Service-->>Controller: Meeting
    Controller->>Admin: fragment + success message

    Admin->>Controller: POST /admin/meetings/{id}/agenda-items (caseRecordId)
    Controller->>Service: addCaseRecordToMeeting(meetingId, caseRecordId)
    Service->>Repos: fetch Meeting, CaseRecord
    Repos-->>Service: entities found
    Service->>Repos: save MeetingAgendaItem (agendaOrder=next)
    Repos->>DB: INSERT agenda_item
    DB-->>Repos: id assigned
    Repos-->>Service: persisted
    Service-->>Controller: AgendaItem
    Controller->>Admin: fragment + success message
Loading
sequenceDiagram
    participant Admin as Admin UI
    participant Controller as MeetingController
    participant Service as MeetingService
    participant Repos as Repositories<br/>(AgendaItem, CaseFile, MeetingAgendaDocument)
    participant DB as Database

    Admin->>Controller: POST /admin/meetings/.../documents (caseFileId)
    Controller->>Service: addDocumentToAgendaItem(meetingId, agendaItemId, caseFileId)
    Service->>Repos: fetch AgendaItem, CaseFile
    Repos-->>Service: entities found
    Service->>Service: validate case-file belongs to case record & no duplicate
    Service->>Repos: save MeetingAgendaDocument
    Repos->>DB: INSERT meeting_agenda_document
    DB-->>Repos: id assigned
    Repos-->>Service: persisted
    Service-->>Controller: MeetingAgendaDocument
    Controller->>Admin: fragment + success message
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • gvaguirres
  • JohanHiths

Poem

🐰 I hopped through code with eager paws,
Made meetings, agendas, and tiny laws.
Files now guard their meeting place,
Orders dance into their space,
Cheer the feature—hop, hooray! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature being added—meeting support integrated with registry, case record, and case file relationships—which is the primary focus of this large changeset.
Linked Issues check ✅ Passed The PR links to issue #47 which requests completing meeting functionality. The changeset comprehensively implements meeting entities, repositories, services, controllers, UI templates, and tests, fully addressing the completion objective.
Out of Scope Changes check ✅ Passed All changes are scoped to meeting implementation and supporting infrastructure. Minor changes to CaseFileService (FileInUseException check) and exception handlers are directly necessary to integrate meetings with existing case file functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/meeting-with-registry-caserecord-casefile-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (8)
src/main/resources/templates/fragments/admin-sidenav.html (1)

39-47: Nit: the new "Sammanträden" link reuses the same icon as "Bokningar".

Line 44 uses fa-calendar-days, which is already used for the "Bokningar" entry on line 34. Consider a distinct icon (e.g., fa-users, fa-gavel, fa-handshake, fa-people-group) so users can visually differentiate the two menu items at a glance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/fragments/admin-sidenav.html` around lines 39 -
47, The "Sammanträden" nav item currently reuses the calendar icon class
"fa-calendar-days" (the anchor with hx-get="/admin/meetings" and inner text
"Sammanträden"); change its <i> icon class to a distinct Font Awesome icon such
as "fa-gavel", "fa-users", "fa-handshake" or "fa-people-group" so it doesn’t
duplicate the "Bokningar" icon and users can visually differentiate the two menu
entries.
src/main/java/backendlab/team4you/exceptions/InvalidMeetingStateException.java (1)

3-7: Consider adding a cause-preserving constructor.

All four new exceptions in this PR (InvalidMeetingStateException, MeetingAgendaItemNotFoundException, DuplicateMeetingAgendaItemException, DuplicateMeetingAgendaDocumentException) only expose a (String) constructor. If any of them is ever used to wrap a lower-level failure (DB constraint violation, optimistic lock, etc.), the root cause will be lost from logs. Adding a (String, Throwable) overload is cheap insurance.

Proposed change (apply the same pattern to all four exception classes)
 public class InvalidMeetingStateException extends RuntimeException {
     public InvalidMeetingStateException(String message) {
         super(message);
     }
+
+    public InvalidMeetingStateException(String message, Throwable cause) {
+        super(message, cause);
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/backendlab/team4you/exceptions/InvalidMeetingStateException.java`
around lines 3 - 7, Add a cause-preserving constructor to the
InvalidMeetingStateException so callers can wrap underlying errors; specifically
add an overload InvalidMeetingStateException(String message, Throwable cause)
that calls super(message, cause). Apply the same pattern to
MeetingAgendaItemNotFoundException, DuplicateMeetingAgendaItemException, and
DuplicateMeetingAgendaDocumentException to preserve root causes when these
exceptions wrap lower-level failures.
src/main/resources/templates/admin-layout.html (1)

18-25: Consider adding Subresource Integrity (SRI) hash to the HTMX CDN load for security.

The HTMX library is loaded from an external CDN (unpkg.com) without verification. While the current approach of loading at the bottom of <body> works correctly for attribute-based HTMX usage (hx-post, hx-get, etc.) since HTMX processes the DOM on DOMContentLoaded, it's recommended to pin the version with an SRI hash to prevent potential tampering:

<script src="https://unpkg.com/htmx.org@1.9.12" 
        integrity="sha384-..." 
        crossorigin="anonymous"></script>

Alternatively, serve HTMX locally to avoid external dependency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/templates/admin-layout.html` around lines 18 - 25, The
HTMX script tag in admin-layout.html currently loads
https://unpkg.com/htmx.org@1.9.12 without verification; update the <script>
element that loads htmx.org (the HTMX CDN include at the bottom of the template)
to include a Subresource Integrity (integrity="sha384-...") attribute and
crossorigin="anonymous" to pin and verify the file, or replace the CDN reference
by serving the htmx.org asset locally and updating the script src to the local
path to eliminate the external dependency.
src/main/java/backendlab/team4you/casefile/CaseFileService.java (1)

182-194: Minor: reorder the in-use check before capturing s3Key and keep delete ordering safe.

s3Key is read before the FileInUseException check; harmless since it's just a local variable read, but it reads a little oddly. More importantly, the current flow deletes the DB row first and then the S3 object — if the S3 delete fails, the transaction rolls back (due to the rethrow) and the CaseFile row remains while the S3 object may or may not have been deleted. Consider deleting S3 after the transaction commits (e.g., TransactionSynchronizationManager) or accept that the DB delete should only roll back on failures you can guarantee are pre-S3.

♻️ Minor reorder for readability
-        String s3Key = caseFile.getS3Key();
-        if (meetingAgendaDocumentRepository.existsByCaseFileId(fileId)) {
+        if (meetingAgendaDocumentRepository.existsByCaseFileId(fileId)) {
             throw new FileInUseException("Filen kan inte tas bort eftersom den används som mötesunderlag.");
         }
 
+        String s3Key = caseFile.getS3Key();
         caseFileRepository.delete(caseFile);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/backendlab/team4you/casefile/CaseFileService.java` around lines
182 - 194, The current method reads s3Key before checking
meetingAgendaDocumentRepository.existsByCaseFileId(fileId) and deletes the DB
row (caseFileRepository.delete) before attempting s3Service.deleteFile, which
can lead to inconsistent state if S3 deletion fails; reorder to check
existsByCaseFileId(fileId) first, then capture s3Key, and move the S3 delete to
run after the successful DB commit (use
TransactionSynchronizationManager.registerSynchronization or an afterCommit
callback) so caseFileRepository.delete runs inside the transaction and
s3Service.deleteFile runs only after commit, logging and handling exceptions
from s3Service.deleteFile without causing the DB transaction to roll back.
src/main/resources/db/migration/V19__create_meeting_tables.sql (1)

1-50: Add indexes on foreign-key columns.

PostgreSQL does not automatically index FK columns. The following queries/cascades will do sequential scans without them:

  • meeting.registry_id — used by findByRegistryOrderByStartsAtAsc/Desc.
  • meeting_agenda_item.meeting_id — used by findByMeetingOrderByAgendaOrderAsc and by ON DELETE CASCADE from meeting.
  • meeting_agenda_item.case_record_id — needed for efficient lookup/constraint checks.
  • meeting_agenda_document.agenda_item_id — used by findByAgendaItem and ON DELETE CASCADE.
  • meeting_agenda_document.case_file_id — used by existsByCaseFileId on every file deletion.

Note: the composite UNIQUE constraints already provide an index whose leading column is meeting_id / agenda_item_id, so those specific cases may be partially covered, but standalone indexes on registry_id, case_record_id, and case_file_id are still needed.

♻️ Suggested indexes
+CREATE INDEX idx_meeting_registry_id ON meeting(registry_id);
+CREATE INDEX idx_meeting_agenda_item_case_record_id ON meeting_agenda_item(case_record_id);
+CREATE INDEX idx_meeting_agenda_document_case_file_id ON meeting_agenda_document(case_file_id);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/db/migration/V19__create_meeting_tables.sql` around lines
1 - 50, Add explicit indexes for the FK columns to avoid sequential scans:
create indexes on meeting(registry_id) (used by
findByRegistryOrderByStartsAtAsc/Desc), meeting_agenda_item(case_record_id) (for
efficient lookup/constraint checks), and meeting_agenda_document(case_file_id)
(used by existsByCaseFileId). Also ensure there are indexes on
meeting_agenda_item(meeting_id) and meeting_agenda_document(agenda_item_id) to
support findByMeetingOrderByAgendaOrderAsc, findByAgendaItem and ON DELETE
CASCADE (note UNIQUE constraints may already provide an index for the leading
column, but add standalone CREATE INDEX statements if those indexes are not
present).
src/main/java/backendlab/team4you/meeting/MeetingAgendaItem.java (1)

9-94: LGTM.

Mapping aligns with the migration (unique constraint names/columns, FK columns, nullability, note length). Cascade/orphan-removal on documents is appropriate for this aggregate.

Nit: getDocuments() returns the backing list directly, so external callers could mutate it outside Hibernate's awareness. If you want stricter encapsulation, consider returning Collections.unmodifiableList(documents) and adding addDocument/removeDocument helpers — optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/backendlab/team4you/meeting/MeetingAgendaItem.java` around
lines 9 - 94, getDocuments() currently returns the mutable backing field
documents which allows external mutation; change it to return an unmodifiable
view (e.g. Collections.unmodifiableList(documents)) and add helper methods
addDocument(MeetingAgendaDocument doc) and removeDocument(MeetingAgendaDocument
doc) that manage the documents list and set/unset the bidirectional link (ensure
MeetingAgendaDocument.agendaItem is updated) so all mutations go through
controlled methods.
src/main/java/backendlab/team4you/meeting/MeetingService.java (1)

140-141: Prefer domain exceptions over IllegalArgumentException for not-found cases.

The service uses dedicated MeetingNotFoundException, MeetingAgendaItemNotFoundException, and MeetingAgendaDocumentNotFoundException elsewhere, but falls back to IllegalArgumentException for missing Registry (line 141), CaseRecord (line 175), and MeetingAgendaDocument (line 317). This inconsistency also triggers the controller-side issue flagged on MeetingController (narrow catch (IllegalArgumentException) only partially covers this path).

Consider using RegistryNotFoundException / a CaseRecordNotFoundException / MeetingAgendaDocumentNotFoundException consistently so GlobalRestExceptionHandler can map them uniformly to 404s.

Also applies to: 174-175, 316-317

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/backendlab/team4you/meeting/MeetingService.java` around lines
140 - 141, Replace the use of IllegalArgumentException in MeetingService when
entities are not found with the appropriate domain-specific not-found exceptions
so the global handler maps to 404s: change the
registryRepository.findById(...).orElseThrow(() -> new
IllegalArgumentException(...)) to throw RegistryNotFoundException, the
caseRecordRepository.findById(...).orElseThrow(...) to throw
CaseRecordNotFoundException, and the
meetingAgendaDocumentRepository.findById(...).orElseThrow(...) to throw
MeetingAgendaDocumentNotFoundException (preserve or adapt the existing error
message/constructor signature of each exception); these changes should be
applied in the methods inside MeetingService where Registry, CaseRecord and
MeetingAgendaDocument lookups occur so controllers and
GlobalRestExceptionHandler handle them consistently.
src/main/java/backendlab/team4you/meeting/Meeting.java (1)

45-86: Consider Spring Data JPA Auditing for createdAt/updatedAt (optional).

The manual @PrePersist/@PreUpdate approach works correctly, but Spring Data JPA's @CreatedDate/@LastModifiedDate (with @EntityListeners(AuditingEntityListener.class) and @EnableJpaAuditing) removes the boilerplate and aligns timestamps with auditor infrastructure if you ever add user tracking.

Not blocking — flag only if the rest of the codebase has adopted auditing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/backendlab/team4you/meeting/Meeting.java` around lines 45 - 86,
The Meeting entity currently sets createdAt/updatedAt in onCreate()/onUpdate()
with `@PrePersist/`@PreUpdate; replace this manual handling by annotating the
createdAt and updatedAt fields with Spring Data JPA's `@CreatedDate` and
`@LastModifiedDate`, add `@EntityListeners`(AuditingEntityListener.class) to the
Meeting class, and remove the onCreate/onUpdate lifecycle methods; ensure JPA
auditing is enabled in the application (e.g., `@EnableJpaAuditing` in a config) so
timestamps are auto-populated and compatible with future auditor tracking.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docker-compose.yaml`:
- Around line 25-33: The docker-compose currently mounts the named volume
localstack_data:/var/lib/localstack which does not provide reliable S3
persistence in LocalStack Community Edition; either remove the localstack_data
volume to keep the compose file minimal if you intend to re-seed state on every
start, or document/implement a persistence solution: upgrade to LocalStack Pro
and set the PERSISTENCE env var to "1" (and update the service environment in
compose), or integrate a Community workaround such as GREsau/localstack-persist
and mount its hooks into the LocalStack container; reference the localstack_data
volume and the PERSISTENCE environment variable when applying the chosen change.

In `@src/main/java/backendlab/team4you/casefile/CaseFileService.java`:
- Around line 38-52: The tests in CaseFileServiceTest are missing a mock for
MeetingAgendaDocumentRepository which causes NPE when CaseFileService.deleteFile
calls meetingAgendaDocumentRepository.existsByCaseFileId(...); add a field
annotated with `@Mock` for MeetingAgendaDocumentRepository in CaseFileServiceTest
and in each of the three deleteFile test methods add a stub:
when(meetingAgendaDocumentRepository.existsByCaseFileId(anyLong())).thenReturn(false);
so the service method can run without hitting a null repository.

In
`@src/main/java/backendlab/team4you/exceptions/GlobalRestExceptionHandler.java`:
- Around line 209-213: The FileInUseException handler returns a raw String which
breaks the consistent error envelope; update GlobalRestExceptionHandler so
handleFileInUse(FileInUseException) returns ResponseEntity<ErrorResponseDto>
using the same ErrorResponseDto structure (status, error, message, timestamp) as
other handlers, or simply add FileInUseException.class to the existing
handleConflict `@ExceptionHandler` list; ensure you populate the ErrorResponseDto
fields the same way other handlers do and return
ResponseEntity.status(HttpStatus.CONFLICT).body(theDto).

In
`@src/main/java/backendlab/team4you/exceptions/GlobalViewExceptionHandler.java`:
- Line 12: Update GlobalViewExceptionHandler.handleNotFound to include the two
meeting-specific exceptions so they map to 404: add MeetingNotFoundException and
MeetingAgendaDocumentNotFoundException to the list/condition handled by
handleNotFound (the same place where other NotFound exceptions are checked) so
that when these exceptions are thrown by methods like showMeeting,
addAgendaItem, moveAgendaItemUp/Down, removeAgendaItem, populateMeetingsPage or
addAgendaDocument they are caught and return the existing 404 response instead
of falling through to handleUnexpected.

In `@src/main/java/backendlab/team4you/meeting/MeetingAgendaItemRepository.java`:
- Line 13: Remove the malformed repository method declaration findById(Meeting,
CaseRecord) from MeetingAgendaItemRepository because it conflicts with
JpaRepository's reserved findById(ID) signature and causes Spring Data startup
errors; delete that method line and keep relying on the existing JpaRepository
findById(Long) and the existsByMeetingAndCaseRecord method for any
meeting+caseRecord checks (note MeetingService uses findById(agendaItemId)),
then run the app/tests to verify Spring Data initializes cleanly.

In `@src/main/java/backendlab/team4you/meeting/MeetingController.java`:
- Around line 175-180: The catch blocks in MeetingController around calls to
meetingService.addCaseRecordToMeeting and removeAgendaItem are too narrow
(catching IllegalArgumentException) so domain exceptions like
DuplicateMeetingAgendaItemException, InvalidMeetingStateException, and
MeetingAgendaItemNotFoundException bubble up; update those handlers to catch the
specific domain exceptions (e.g., catch InvalidMeetingStateException |
DuplicateMeetingAgendaItemException | MeetingAgendaItemNotFoundException) or use
the same broader Exception strategy as used for
moveAgendaItemUp/moveAgendaItemDown and addAgendaDocument/removeAgendaDocument,
and ensure you set model.addAttribute("errorMessage", exception.getMessage()) so
the fragment renders the error consistently.
- Line 74: Multiple success messages in MeetingController are inconsistently
capitalized; update each model.addAttribute("successMessage", "...") call so the
message text uses sentence case (capitalize the first letter) for consistency.
Specifically change "sammanträdet skapades.", "sammanträdet uppdaterades.",
"sammanträdet togs bort.", "ärendet lades till på sammanträdet.", and
"dagordningspunkten togs bort." to start with an uppercase letter, keeping the
rest of the text unchanged; locate these strings in MeetingController.java (the
model.addAttribute("successMessage", ...) calls) and make the capitalization
edits across the create/update/delete/add/remove handlers to normalize UX
messaging.
- Around line 119-123: The catch block in MeetingController around updateMeeting
is too broad and re-fetching the meeting can re-throw MeetingNotFoundException
and hide the original error; change the error handling so you capture
exception.getMessage() into a local variable first, avoid a blanket
catch(Exception) (catch specific exceptions like MeetingNotFoundException
separately), and guard the call to meetingService.getMeetingById(meetingId) with
its own try/catch: if getMeetingById throws MeetingNotFoundException then add
the original message to the model and call populateMeetingsPage in a safe
fallback path (or redirect to the meetings list) instead of letting the re-fetch
bubble up; refer to updateMeeting, meetingService.getMeetingById,
populateMeetingsPage in your changes.

In `@src/main/java/backendlab/team4you/meeting/MeetingService.java`:
- Line 339: In MeetingService where InvalidMeetingStateException is thrown with
the message "Ärendet saknar diarum.", correct the typo in the user-facing string
to "Ärendet saknar diarium." by updating the exception message in the throw site
inside the MeetingService class so the displayed Swedish word is spelled
correctly.

In `@src/main/resources/db/migration/V19__create_meeting_tables.sql`:
- Around line 33-34: The uk_meeting_agenda_item_meeting_order unique constraint
can be violated by resequenceAgendaItems called from removeAgendaItem because
updates are batched until commit; either modify the migration
(V19__create_meeting_tables.sql) to make CONSTRAINT
uk_meeting_agenda_item_meeting_order DEFERRABLE INITIALLY IMMEDIATE, or change
resequenceAgendaItems/removeAgendaItem to use the same safe two-step pattern as
moveAgendaItemUp/moveAgendaItemDown (assign a temporary out-of-range value like
0, call saveAndFlush() for that row, then assign the final new order and
saveAndFlush()) so no intermediate unique-key collisions occur.

In `@src/main/resources/templates/fragments/admin-meetings.html`:
- Around line 97-100: The label "Registry:" is in English while the rest of the
fragment uses Swedish; update the static label text next to the span that binds
to selectedMeeting.registry.name to "Diarium" so it matches the create form and
UI language consistency (look for the <span
th:text="${selectedMeeting.registry.name}">Kommunstyrelsen</span> and its
surrounding <p> block and replace the "Registry:" text with "Diarium").

In `@src/test/java/backendlab/team4you/meeting/MeetingControllerTest.java`:
- Around line 86-93: Tests in MeetingControllerTest are inconsistent about CSRF:
ensure every mockMvc.perform POST to /admin/meetings includes a CSRF token so
they reflect SecurityConfig; update the POST invocations in the tests
createMeeting_shouldReturnFragmentAndSuccessMessage (the POST at lines 86–93),
addAgendaItem_shouldReturnFragmentAndSuccessMessage,
updateMeeting_shouldReturnFragmentAndSuccessMessage, and
deleteMeeting_shouldReturnFragmentAndSuccessMessage to include .with(csrf())
just like createMeeting_shouldReturnFragmentAndErrorMessage_whenServiceThrows
does, so all POST tests consistently simulate CSRF-protected requests.

---

Nitpick comments:
In `@src/main/java/backendlab/team4you/casefile/CaseFileService.java`:
- Around line 182-194: The current method reads s3Key before checking
meetingAgendaDocumentRepository.existsByCaseFileId(fileId) and deletes the DB
row (caseFileRepository.delete) before attempting s3Service.deleteFile, which
can lead to inconsistent state if S3 deletion fails; reorder to check
existsByCaseFileId(fileId) first, then capture s3Key, and move the S3 delete to
run after the successful DB commit (use
TransactionSynchronizationManager.registerSynchronization or an afterCommit
callback) so caseFileRepository.delete runs inside the transaction and
s3Service.deleteFile runs only after commit, logging and handling exceptions
from s3Service.deleteFile without causing the DB transaction to roll back.

In
`@src/main/java/backendlab/team4you/exceptions/InvalidMeetingStateException.java`:
- Around line 3-7: Add a cause-preserving constructor to the
InvalidMeetingStateException so callers can wrap underlying errors; specifically
add an overload InvalidMeetingStateException(String message, Throwable cause)
that calls super(message, cause). Apply the same pattern to
MeetingAgendaItemNotFoundException, DuplicateMeetingAgendaItemException, and
DuplicateMeetingAgendaDocumentException to preserve root causes when these
exceptions wrap lower-level failures.

In `@src/main/java/backendlab/team4you/meeting/Meeting.java`:
- Around line 45-86: The Meeting entity currently sets createdAt/updatedAt in
onCreate()/onUpdate() with `@PrePersist/`@PreUpdate; replace this manual handling
by annotating the createdAt and updatedAt fields with Spring Data JPA's
`@CreatedDate` and `@LastModifiedDate`, add
`@EntityListeners`(AuditingEntityListener.class) to the Meeting class, and remove
the onCreate/onUpdate lifecycle methods; ensure JPA auditing is enabled in the
application (e.g., `@EnableJpaAuditing` in a config) so timestamps are
auto-populated and compatible with future auditor tracking.

In `@src/main/java/backendlab/team4you/meeting/MeetingAgendaItem.java`:
- Around line 9-94: getDocuments() currently returns the mutable backing field
documents which allows external mutation; change it to return an unmodifiable
view (e.g. Collections.unmodifiableList(documents)) and add helper methods
addDocument(MeetingAgendaDocument doc) and removeDocument(MeetingAgendaDocument
doc) that manage the documents list and set/unset the bidirectional link (ensure
MeetingAgendaDocument.agendaItem is updated) so all mutations go through
controlled methods.

In `@src/main/java/backendlab/team4you/meeting/MeetingService.java`:
- Around line 140-141: Replace the use of IllegalArgumentException in
MeetingService when entities are not found with the appropriate domain-specific
not-found exceptions so the global handler maps to 404s: change the
registryRepository.findById(...).orElseThrow(() -> new
IllegalArgumentException(...)) to throw RegistryNotFoundException, the
caseRecordRepository.findById(...).orElseThrow(...) to throw
CaseRecordNotFoundException, and the
meetingAgendaDocumentRepository.findById(...).orElseThrow(...) to throw
MeetingAgendaDocumentNotFoundException (preserve or adapt the existing error
message/constructor signature of each exception); these changes should be
applied in the methods inside MeetingService where Registry, CaseRecord and
MeetingAgendaDocument lookups occur so controllers and
GlobalRestExceptionHandler handle them consistently.

In `@src/main/resources/db/migration/V19__create_meeting_tables.sql`:
- Around line 1-50: Add explicit indexes for the FK columns to avoid sequential
scans: create indexes on meeting(registry_id) (used by
findByRegistryOrderByStartsAtAsc/Desc), meeting_agenda_item(case_record_id) (for
efficient lookup/constraint checks), and meeting_agenda_document(case_file_id)
(used by existsByCaseFileId). Also ensure there are indexes on
meeting_agenda_item(meeting_id) and meeting_agenda_document(agenda_item_id) to
support findByMeetingOrderByAgendaOrderAsc, findByAgendaItem and ON DELETE
CASCADE (note UNIQUE constraints may already provide an index for the leading
column, but add standalone CREATE INDEX statements if those indexes are not
present).

In `@src/main/resources/templates/admin-layout.html`:
- Around line 18-25: The HTMX script tag in admin-layout.html currently loads
https://unpkg.com/htmx.org@1.9.12 without verification; update the <script>
element that loads htmx.org (the HTMX CDN include at the bottom of the template)
to include a Subresource Integrity (integrity="sha384-...") attribute and
crossorigin="anonymous" to pin and verify the file, or replace the CDN reference
by serving the htmx.org asset locally and updating the script src to the local
path to eliminate the external dependency.

In `@src/main/resources/templates/fragments/admin-sidenav.html`:
- Around line 39-47: The "Sammanträden" nav item currently reuses the calendar
icon class "fa-calendar-days" (the anchor with hx-get="/admin/meetings" and
inner text "Sammanträden"); change its <i> icon class to a distinct Font Awesome
icon such as "fa-gavel", "fa-users", "fa-handshake" or "fa-people-group" so it
doesn’t duplicate the "Bokningar" icon and users can visually differentiate the
two menu entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f1c998b0-2ea4-48bf-8f64-71d0387085ea

📥 Commits

Reviewing files that changed from the base of the PR and between 951357f and 28998e9.

📒 Files selected for processing (31)
  • docker-compose.yaml
  • init-localstack.sh
  • src/main/java/backendlab/team4you/casefile/CaseFileRepository.java
  • src/main/java/backendlab/team4you/casefile/CaseFileService.java
  • src/main/java/backendlab/team4you/casefile/ui/CaseFileViewController.java
  • src/main/java/backendlab/team4you/exceptions/DuplicateMeetingAgendaDocumentException.java
  • src/main/java/backendlab/team4you/exceptions/DuplicateMeetingAgendaItemException.java
  • src/main/java/backendlab/team4you/exceptions/FileInUseException.java
  • src/main/java/backendlab/team4you/exceptions/GlobalRestExceptionHandler.java
  • src/main/java/backendlab/team4you/exceptions/GlobalViewExceptionHandler.java
  • src/main/java/backendlab/team4you/exceptions/InvalidMeetingStateException.java
  • src/main/java/backendlab/team4you/exceptions/MeetingAgendaDocumentNotFoundException.java
  • src/main/java/backendlab/team4you/exceptions/MeetingAgendaItemNotFoundException.java
  • src/main/java/backendlab/team4you/exceptions/MeetingNotFoundException.java
  • src/main/java/backendlab/team4you/meeting/Meeting.java
  • src/main/java/backendlab/team4you/meeting/MeetingAgendaDocument.java
  • src/main/java/backendlab/team4you/meeting/MeetingAgendaDocumentRepository.java
  • src/main/java/backendlab/team4you/meeting/MeetingAgendaItem.java
  • src/main/java/backendlab/team4you/meeting/MeetingAgendaItemRepository.java
  • src/main/java/backendlab/team4you/meeting/MeetingController.java
  • src/main/java/backendlab/team4you/meeting/MeetingRepository.java
  • src/main/java/backendlab/team4you/meeting/MeetingService.java
  • src/main/java/backendlab/team4you/meeting/MeetingStatus.java
  • src/main/resources/db/migration/V19__create_meeting_tables.sql
  • src/main/resources/static/css/admin.css
  • src/main/resources/static/css/meetings.css
  • src/main/resources/templates/admin-layout.html
  • src/main/resources/templates/fragments/admin-meetings.html
  • src/main/resources/templates/fragments/admin-sidenav.html
  • src/test/java/backendlab/team4you/meeting/MeetingControllerTest.java
  • src/test/java/backendlab/team4you/meeting/MeetingServiceTest.java

Comment thread docker-compose.yaml Outdated
Comment thread src/main/java/backendlab/team4you/casefile/CaseFileService.java
Comment thread src/main/java/backendlab/team4you/meeting/MeetingAgendaItemRepository.java Outdated
Comment thread src/main/java/backendlab/team4you/meeting/MeetingController.java
Comment thread src/main/java/backendlab/team4you/meeting/MeetingService.java Outdated
Comment on lines +33 to +34
CONSTRAINT uk_meeting_agenda_item_meeting_order
UNIQUE (meeting_id, agenda_order)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the resequencing logic and its call sites to confirm the concern.
rg -nP -C3 '\bresequenceAgendaItems\b'
rg -nP -C5 '\bsetAgendaOrder\b'

Repository: ithsjava25/project-backend-team4you

Length of output: 6956


🏁 Script executed:

# Find the full removeAgendaItem method and its annotations
rg -nP -B5 -A15 'public void removeAgendaItem'

# Check if there are any tests or comments about unique constraint issues
rg -i 'unique|constraint|resequence' src/main/java/backendlab/team4you/meeting/ -A2 -B2

Repository: ithsjava25/project-backend-team4you

Length of output: 4674


🏁 Script executed:

# Check class-level `@Transactional` and method signatures around removeAgendaItem
rg -nP -B20 'public void removeAgendaItem' src/main/java/backendlab/team4you/meeting/MeetingService.java | head -30

# Look for `@Transactional` annotations on the class or method
rg -nP '@Transactional|class MeetingService' src/main/java/backendlab/team4you/meeting/MeetingService.java | head -20

Repository: ithsjava25/project-backend-team4you

Length of output: 1257


resequenceAgendaItems risks violating uk_meeting_agenda_item_meeting_order constraint during deletion

When removeAgendaItem deletes an agenda item and calls resequenceAgendaItems, the resequencing sets new order values (1..N) in memory without explicit flush. Unlike moveAgendaItemUp and moveAgendaItemDown (which use intermediate 0-values and saveAndFlush() per update), resequenceAgendaItems batches all updates until transaction commit. If the deleted row remains visible during constraint checks, reassignments that collide with the deleted row's order will fail.

Example: deleting item with order 2 from [1, 2, 3, 4] → resequence remaining to [1, 2, 3]; item 3→2 violates the unique constraint if the delete hasn't been flushed first.

Make the constraint DEFERRABLE INITIALLY IMMEDIATE in the migration, or apply the same safe pattern used in moveAgendaItemUp/Down: set to temporary out-of-range value first (e.g., 0), flush, then set final values.

Option 1: Make constraint deferrable
                                     CONSTRAINT uk_meeting_agenda_item_meeting_order
-                                        UNIQUE (meeting_id, agenda_order)
+                                        UNIQUE (meeting_id, agenda_order) DEFERRABLE INITIALLY IMMEDIATE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/resources/db/migration/V19__create_meeting_tables.sql` around lines
33 - 34, The uk_meeting_agenda_item_meeting_order unique constraint can be
violated by resequenceAgendaItems called from removeAgendaItem because updates
are batched until commit; either modify the migration
(V19__create_meeting_tables.sql) to make CONSTRAINT
uk_meeting_agenda_item_meeting_order DEFERRABLE INITIALLY IMMEDIATE, or change
resequenceAgendaItems/removeAgendaItem to use the same safe two-step pattern as
moveAgendaItemUp/moveAgendaItemDown (assign a temporary out-of-range value like
0, call saveAndFlush() for that row, then assign the final new order and
saveAndFlush()) so no intermediate unique-key collisions occur.

Comment thread src/main/resources/templates/fragments/admin-meetings.html
…thout reliable persistence won't preserve LocalStack state across container restarts in Community Edition.
…eeting_agenda_item_meeting_order constraint during deletion

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/test/java/backendlab/team4you/casefile/CaseFileServiceTest.java (1)

652-673: Solid coverage for the new FileInUseException branch.

The test correctly exercises the order of checks in CaseFileService.deleteFile: permission → referential-use → delete. Asserting on the "mötesunderlag" substring keeps the test robust to message tweaks while still validating the domain reason, and the never() verifications on both caseFileRepository.delete and s3Service.deleteFile confirm the guard aborts the deletion entirely.

One optional tightening, if you want it: you could additionally verify(meetingAgendaDocumentRepository).existsByCaseFileId(100L); to explicitly pin that the in-use check is the one that triggered, but it's not essential given the exception type/message assertions already narrow it down.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/backendlab/team4you/casefile/CaseFileServiceTest.java` around
lines 652 - 673, Add an explicit verification that the in-use check was invoked:
in the test method
deleteFile_shouldThrowFileInUseException_whenFileIsUsedByMeetingAgendaDocument,
after asserting the FileInUseException, call
verify(meetingAgendaDocumentRepository).existsByCaseFileId(100L) to pin that the
meetingAgendaDocumentRepository.existsByCaseFileId check is what prevented
deletion; keep the existing never() verifications for caseFileRepository.delete
and s3Service.deleteFile.
src/main/java/backendlab/team4you/meeting/MeetingService.java (1)

183-193: count + 1 for next agenda order is fragile if orders ever drift from 1..N.

countByMeeting(meeting) + 1 relies on resequenceAgendaItems keeping orders contiguous. Any data drift (failed resequence, external DB change, future code path that deletes without resequencing) leaves a gap whose MAX(agendaOrder) >= count + 1, and the uk_meeting_agenda_item_meeting_order unique constraint fails on insert.

Consider deriving the next order from the current max instead — add a findTopByMeetingOrderByAgendaOrderDesc finder (or a @Query returning COALESCE(MAX(agenda_order), 0)) and use that for nextAgendaOrder. Defensive but cheap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/backendlab/team4you/meeting/MeetingService.java` around lines
183 - 193, Replace the fragile nextAgendaOrder calculation that uses
countByMeeting(meeting) + 1 with a max-based approach: add a finder on
meetingAgendaItemRepository such as
findTopByMeetingOrderByAgendaOrderDesc(Meeting meeting) (or a `@Query` to return
COALESCE(MAX(agenda_order),0)), retrieve the current max agendaOrder for the
given meeting, compute nextAgendaOrder = max + 1, and use that value when
constructing the MeetingAgendaItem in MeetingService; also ensure any null
result from the finder is treated as 0 so the first item becomes 1.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/backendlab/team4you/meeting/MeetingController.java`:
- Around line 169-232: The handlers addAgendaItem, moveAgendaItemUp,
moveAgendaItemDown (and similarly removeAgendaItem, addAgendaDocument,
removeAgendaDocument) call meetingService.getMeetingById(...) before the
try/catch so a MeetingNotFoundException can escape and break HTMX fragment
handling; move the getMeetingById call inside each method's try block (or
defensively resolve a registryId fallback as done in updateMeeting) so the catch
block can handle MeetingNotFoundException and still call
populateMeetingsPage(registryId, meetingId) with a sensible fallback; update
each referenced method (addAgendaItem, moveAgendaItemUp, moveAgendaItemDown,
removeAgendaItem, addAgendaDocument, removeAgendaDocument) to obtain Meeting or
registryId inside the try and keep populateMeetingsPage usage unchanged.
- Around line 75-85: The catch in createMeeting currently calls
populateMeetingsPage(model, registryId, null) which can re-throw when registryId
is invalid; wrap that call in its own try/catch (same pattern used in
updateMeeting) so that if meetingService.getMeetingsForRegistry(registryId)
throws RegistryNotFoundException or IllegalArgumentException you fallback to
populating the page without the registry (e.g., call populateMeetingsPage(model,
null, null) or otherwise call the getAllMeetings path) and preserve the original
model attribute "errorMessage" so the friendly fragment is rendered instead of a
500.

In `@src/main/java/backendlab/team4you/meeting/MeetingService.java`:
- Around line 138-150: Several not-found/null checks in MeetingService use
IllegalArgumentException causing 400 responses; replace them with the
appropriate 404/validation exceptions so HTTP statuses match other methods: in
getMeetingsForRegistry replace the IllegalArgumentException with
RegistryNotFoundException when registryRepository.findById(...) is empty; in
addCaseRecordToMeeting replace IllegalArgumentException("Ärendet hittades
inte.") with CaseRecordNotFoundException when the case record lookup fails; in
removeDocumentFromAgendaItem replace
IllegalArgumentException("Dokumentkopplingen hittades inte.") with
MeetingAgendaDocumentNotFoundException when the document-link lookup fails; and
in updateMeetingStatus replace IllegalArgumentException("Status måste anges.")
with InvalidMeetingStateException for null/invalid status to match other
null-argument validations.

In `@src/main/resources/templates/fragments/admin-meetings.html`:
- Around line 117-120: The template is rendering raw enum names via
${selectedMeeting.status} and using the enum value as the option label; update
the UI to show localized Swedish labels instead. Modify the MeetingStatus enum
to expose a display helper (e.g., getLabel() returning localized text) or add
message keys for each enum value in MessageSource, then update the template
places that reference selectedMeeting.status and the <option> labels (the span
showing the status and the status dropdown option text) to use either
selectedMeeting.status.label (or selectedMeeting.getStatus().getLabel()) or
`#messages`['meeting.status.' + selectedMeeting.status] (and similarly for the
option iteration) so both the detail view and the dropdown show localized
strings.

---

Nitpick comments:
In `@src/main/java/backendlab/team4you/meeting/MeetingService.java`:
- Around line 183-193: Replace the fragile nextAgendaOrder calculation that uses
countByMeeting(meeting) + 1 with a max-based approach: add a finder on
meetingAgendaItemRepository such as
findTopByMeetingOrderByAgendaOrderDesc(Meeting meeting) (or a `@Query` to return
COALESCE(MAX(agenda_order),0)), retrieve the current max agendaOrder for the
given meeting, compute nextAgendaOrder = max + 1, and use that value when
constructing the MeetingAgendaItem in MeetingService; also ensure any null
result from the finder is treated as 0 so the first item becomes 1.

In `@src/test/java/backendlab/team4you/casefile/CaseFileServiceTest.java`:
- Around line 652-673: Add an explicit verification that the in-use check was
invoked: in the test method
deleteFile_shouldThrowFileInUseException_whenFileIsUsedByMeetingAgendaDocument,
after asserting the FileInUseException, call
verify(meetingAgendaDocumentRepository).existsByCaseFileId(100L) to pin that the
meetingAgendaDocumentRepository.existsByCaseFileId check is what prevented
deletion; keep the existing never() verifications for caseFileRepository.delete
and s3Service.deleteFile.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1c5a59e9-4d7b-4584-ab3a-9cb568ffa889

📥 Commits

Reviewing files that changed from the base of the PR and between 28998e9 and 00849ea.

📒 Files selected for processing (10)
  • docker-compose.yaml
  • src/main/java/backendlab/team4you/exceptions/GlobalRestExceptionHandler.java
  • src/main/java/backendlab/team4you/exceptions/GlobalViewExceptionHandler.java
  • src/main/java/backendlab/team4you/meeting/MeetingAgendaItemRepository.java
  • src/main/java/backendlab/team4you/meeting/MeetingController.java
  • src/main/java/backendlab/team4you/meeting/MeetingService.java
  • src/main/resources/db/migration/V20__make_meeting_agenda_order_constraint_deferrable.sql
  • src/main/resources/templates/fragments/admin-meetings.html
  • src/test/java/backendlab/team4you/casefile/CaseFileServiceTest.java
  • src/test/java/backendlab/team4you/meeting/MeetingControllerTest.java
✅ Files skipped from review due to trivial changes (2)
  • src/main/resources/db/migration/V20__make_meeting_agenda_order_constraint_deferrable.sql
  • src/main/java/backendlab/team4you/meeting/MeetingAgendaItemRepository.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/backendlab/team4you/exceptions/GlobalViewExceptionHandler.java
  • src/test/java/backendlab/team4you/meeting/MeetingControllerTest.java

Comment thread src/main/java/backendlab/team4you/meeting/MeetingController.java
Comment thread src/main/java/backendlab/team4you/meeting/MeetingController.java
Comment on lines +138 to +150
@Transactional(readOnly = true)
public List<Meeting> getMeetingsForRegistry(Long registryId) {
Registry registry = registryRepository.findById(registryId)
.orElseThrow(() -> new IllegalArgumentException("Registry hittades inte."));

return meetingRepository.findByRegistryOrderByStartsAtDesc(registry);
}

@Transactional(readOnly = true)
public Meeting getMeetingById(Long meetingId) {
return meetingRepository.findById(meetingId)
.orElseThrow(() -> new MeetingNotFoundException("Sammanträdet hittades inte."));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent exception types for not-found lookups produce wrong HTTP status.

getMeetingsForRegistry throws IllegalArgumentException when the registry doesn't exist (line 141), which GlobalRestExceptionHandler.handleBadRequest maps to 400 Bad Request. Compare with createMeeting (line 68) which throws RegistryNotFoundException404 Not Found. The same inconsistency shows up in:

  • addCaseRecordToMeeting line 175: IllegalArgumentException("Ärendet hittades inte.") — should be CaseRecordNotFoundException (404)
  • removeDocumentFromAgendaItem line 317: IllegalArgumentException("Dokumentkopplingen hittades inte.") — should be MeetingAgendaDocumentNotFoundException (404)
  • updateMeetingStatus line 328: IllegalArgumentException("Status måste anges.") — should be InvalidMeetingStateException to match the null-argument validations at lines 94, 110, 129
🛠️ Proposed fixes
@@ getMeetingsForRegistry
-        Registry registry = registryRepository.findById(registryId)
-                .orElseThrow(() -> new IllegalArgumentException("Registry hittades inte."));
+        Registry registry = registryRepository.findById(registryId)
+                .orElseThrow(() -> new RegistryNotFoundException("Registry hittades inte."));
@@ addCaseRecordToMeeting
-        CaseRecord caseRecord = caseRecordRepository.findById(caseRecordId)
-                .orElseThrow(() -> new IllegalArgumentException("Ärendet hittades inte."));
+        CaseRecord caseRecord = caseRecordRepository.findById(caseRecordId)
+                .orElseThrow(() -> new CaseRecordNotFoundException("Ärendet hittades inte."));
@@ removeDocumentFromAgendaItem
-        MeetingAgendaDocument document = meetingAgendaDocumentRepository.findById(documentId)
-                .orElseThrow(() -> new IllegalArgumentException("Dokumentkopplingen hittades inte."));
+        MeetingAgendaDocument document = meetingAgendaDocumentRepository.findById(documentId)
+                .orElseThrow(() -> new MeetingAgendaDocumentNotFoundException("Dokumentkopplingen hittades inte."));
@@ updateMeetingStatus
-        if (status == null) {
-            throw new IllegalArgumentException("Status måste anges.");
-        }
+        if (status == null) {
+            throw new InvalidMeetingStateException("Status måste anges.");
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/backendlab/team4you/meeting/MeetingService.java` around lines
138 - 150, Several not-found/null checks in MeetingService use
IllegalArgumentException causing 400 responses; replace them with the
appropriate 404/validation exceptions so HTTP statuses match other methods: in
getMeetingsForRegistry replace the IllegalArgumentException with
RegistryNotFoundException when registryRepository.findById(...) is empty; in
addCaseRecordToMeeting replace IllegalArgumentException("Ärendet hittades
inte.") with CaseRecordNotFoundException when the case record lookup fails; in
removeDocumentFromAgendaItem replace
IllegalArgumentException("Dokumentkopplingen hittades inte.") with
MeetingAgendaDocumentNotFoundException when the document-link lookup fails; and
in updateMeetingStatus replace IllegalArgumentException("Status måste anges.")
with InvalidMeetingStateException for null/invalid status to match other
null-argument validations.

Comment thread src/main/resources/templates/fragments/admin-meetings.html

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/backendlab/team4you/meeting/MeetingController.java`:
- Around line 327-333: The helper populateMeetingsPageAfterMeetingAction
currently only catches MeetingNotFoundException but populateMeetingsPage calls
meetingService.getMeetingsForRegistry(registryId) which can throw
RegistryNotFoundException; update populateMeetingsPageAfterMeetingAction to also
catch RegistryNotFoundException (in the same catch block or an additional one)
and in that case call populateMeetingsPage(model, null, null) just like the
MeetingNotFoundException path so HTMX fragment flows don't break; reference
populateMeetingsPageAfterMeetingAction, populateMeetingsPage,
meetingService.getMeetingsForRegistry, RegistryNotFoundException, and
MeetingNotFoundException when making the change.
- Around line 75-88: The fallback logic incorrectly assumes
registryRepository.findById(registryId) can throw; replace the try/catch with an
existence check and only set safeRegistryId when the registry actually exists:
call registryRepository.findById(registryId).isPresent() (or use
existsById(registryId)) to decide whether to assign safeRegistryId, so
populateMeetingsPage receives null for unknown registries and avoid
re-triggering meetingService.getMeetingsForRegistry which throws
RegistryNotFoundException; keep the original model.addAttribute("errorMessage",
exception.getMessage()) and pass safeRegistryId into populateMeetingsPage.

In `@src/main/java/backendlab/team4you/meeting/MeetingService.java`:
- Around line 293-294: The code that resolves a CaseFile (the statement starting
with "CaseFile caseFile = caseFileRepository.findById(caseFileId) ...") is
throwing MeetingAgendaDocumentNotFoundException; replace that with the correct
CaseFileNotFoundException so the lookup throws new
CaseFileNotFoundException("Handlingen hittades inte.") (or equivalent localized
message) to match the existing GlobalRestExceptionHandler mapping and avoid
misleading logs and API responses.

In `@src/main/resources/templates/fragments/admin-meetings.html`:
- Around line 23-34: The label text for the registry select is inconsistent:
change the <label for="registryId">Instans</label> to use the same Swedish term
"Diarium" so it matches the select placeholder "Välj diarium" and the detail
view label "Diarium:"; update the label associated with the element id
"registryId" (and any related template fragment that references registryId if
present) to "Diarium" to ensure consistent UI terminology.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a253520a-00db-4f52-a773-0a43aa360457

📥 Commits

Reviewing files that changed from the base of the PR and between 00849ea and d2ca6b5.

📒 Files selected for processing (5)
  • src/main/java/backendlab/team4you/meeting/MeetingController.java
  • src/main/java/backendlab/team4you/meeting/MeetingService.java
  • src/main/java/backendlab/team4you/meeting/MeetingStatus.java
  • src/main/resources/templates/fragments/admin-meetings.html
  • src/test/java/backendlab/team4you/meeting/MeetingControllerTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/backendlab/team4you/meeting/MeetingStatus.java
  • src/test/java/backendlab/team4you/meeting/MeetingControllerTest.java

Comment thread src/main/java/backendlab/team4you/meeting/MeetingController.java
Comment thread src/main/java/backendlab/team4you/meeting/MeetingController.java
Comment thread src/main/java/backendlab/team4you/meeting/MeetingService.java Outdated
Comment thread src/main/resources/templates/fragments/admin-meetings.html
… populateMeetingsPage will re-throw on an invalid registryId and populateMeetingsPageAfterMeetingAction won't swallow RegistryNotFoundException.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/main/java/backendlab/team4you/meeting/MeetingService.java (1)

329-332: ⚠️ Potential issue | 🟡 Minor

updateMeetingStatus still throws IllegalArgumentException for null status.

The previous review flagged four sites that used IllegalArgumentException for validation/not-found. The fixes at lines 141, 175, and 320 were applied, but this one at line 331 was missed. It remains inconsistent with the null-argument validations at lines 94, 110, and 129 (all of which throw InvalidMeetingStateException), and will map to a different HTTP status in GlobalRestExceptionHandler.

🛠️ Proposed fix
     public Meeting updateMeetingStatus(Long meetingId, MeetingStatus status) {
         if (status == null) {
-            throw new IllegalArgumentException("Status måste anges.");
+            throw new InvalidMeetingStateException("Status måste anges.");
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/backendlab/team4you/meeting/MeetingService.java` around lines
329 - 332, In MeetingService.updateMeetingStatus(Long meetingId, MeetingStatus
status) replace the thrown IllegalArgumentException for a null status with
InvalidMeetingStateException so null-validation is consistent with other methods
(lines that validate meetingId/status using InvalidMeetingStateException); keep
the same message text (or localize) and ensure the check still occurs at the
start of the method referencing the status parameter and meetingId as before.
🧹 Nitpick comments (1)
src/test/java/backendlab/team4you/meeting/MeetingServiceTest.java (1)

61-61: Unused test fixture.

wrongCaseFile is declared on line 61 and initialized on line 85 but never referenced in any test method. Safe to drop to keep the fixture minimal.

♻️ Proposed cleanup
     private CaseFile caseFile;
-    private CaseFile wrongCaseFile;
         caseFile = mock(CaseFile.class);
-        wrongCaseFile = mock(CaseFile.class);

Also applies to: 85-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/backendlab/team4you/meeting/MeetingServiceTest.java` at line
61, The test fixture variable wrongCaseFile in MeetingServiceTest is declared
and initialized but never used; remove its declaration and the corresponding
initialization in the test setup (where wrongCaseFile is set) to keep the test
fixture minimal, ensuring you only keep variables actually referenced by test
methods (update any setup method that assigns wrongCaseFile to avoid unused
code).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/main/java/backendlab/team4you/meeting/MeetingService.java`:
- Around line 329-332: In MeetingService.updateMeetingStatus(Long meetingId,
MeetingStatus status) replace the thrown IllegalArgumentException for a null
status with InvalidMeetingStateException so null-validation is consistent with
other methods (lines that validate meetingId/status using
InvalidMeetingStateException); keep the same message text (or localize) and
ensure the check still occurs at the start of the method referencing the status
parameter and meetingId as before.

---

Nitpick comments:
In `@src/test/java/backendlab/team4you/meeting/MeetingServiceTest.java`:
- Line 61: The test fixture variable wrongCaseFile in MeetingServiceTest is
declared and initialized but never used; remove its declaration and the
corresponding initialization in the test setup (where wrongCaseFile is set) to
keep the test fixture minimal, ensuring you only keep variables actually
referenced by test methods (update any setup method that assigns wrongCaseFile
to avoid unused code).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3234fb48-2be1-47fc-b810-8c88095c8aef

📥 Commits

Reviewing files that changed from the base of the PR and between d2ca6b5 and 2e2b0aa.

📒 Files selected for processing (4)
  • src/main/java/backendlab/team4you/meeting/MeetingController.java
  • src/main/java/backendlab/team4you/meeting/MeetingService.java
  • src/main/resources/templates/fragments/admin-meetings.html
  • src/test/java/backendlab/team4you/meeting/MeetingServiceTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/backendlab/team4you/meeting/MeetingController.java

@gvaguirres
gvaguirres merged commit 7c61ffa into main Apr 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bygga klart meeting så att det blir användbart

2 participants